package q1_volatile_test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class Student {
    //没有加volatile关键字之前，对主线程不可见
    //int age = 0;

    //volatile是java虚拟机提供的轻量级的同步机制
    //volatile关键字，解决两个问题，1）可见性 2）禁止指令重排（有序性），但是不保证原子性
    volatile int age = 0;

    public void addTo60() {
        this.age = 60;
    }

    /**
     * 解决原子性问题：
     * 1、加synchronized
     * 2、使用原子包装类AtomicInteger
     */
    public void agePlusPlus(){
        this.age++;
    }

    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void addMyAtomic(){
        atomicInteger.getAndIncrement();
    }
}

public class Demo1 {

    public static void main(String[] args) {
//        volatileTest1();

        volatileTest2();
    }

    /**
     * volatile不保证原子性案例演示
     */
    public static void volatileTest2() {
        Student student = new Student();

        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    student.agePlusPlus();
                    student.addMyAtomic();
                }
            },"A" + i).start();
        }

        //需要等待上面20个线程全部计算完成后，再用main线程取得最终的结果值
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t final age = " + student.age);
        System.out.println(Thread.currentThread().getName() + "\t final atomicInteger = " + student.atomicInteger);
    }


    /**
     * volatile可以保证可见性，及时通知其它线程，主物理内存的值已经被修改
     */
    public static void volatileTest1() {
        Student student = new Student();

        new Thread(() -> {
            /*
            JMM三大特性：
            1、可见性：当某个线程修改了变量值，其他线程都能知道其被修改了
            2、原子性：不可分割，完整性，当某个线程正在做具体业务时，中间不可以被加塞或分割，需要整体完整。要么同时成功，要么同时失败。
            3、有序性：计算机在执行程序时，为了提高性能，编译器和处理器常常会对指令做重排，一般分为以下3种：
                源代码 → 编译器优化的重排 → 指令并行的重排 → 内存系统的重排 → 最终执行的指令
                单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致
                处理器在进行重排序时必须考虑指令之间的数据依赖性
                多线程环境中线程交替执行，由于编译器优化重排的存在，两个线程中使用的变量能否保证一致性是无法确定的，结果无法预测
             */
            //每个线程都有自己的工作内存（JMM），当使用到主线程变量时，只是将其值拷贝到当前线程的工作内存中，与主线程的变量没有关联
            System.out.println(Thread.currentThread().getName() +"\t age = " + student.age + ", 3秒后将age改成60");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            student.addTo60();
            //由于工作线程的变量修改对主线程不可见，导致主线程无法得知age已经被修改
        }, "A").start();

        while (student.age == 0) {
            System.out.println("如果值没有被修改，则一直等待");
        }

        System.out.println(Thread.currentThread().getName() + "\t age = " + student.age);
    }
}
